<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>openlayers加载GeoJson</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/ol.min.css"
    integrity="sha512-bc9nJM5uKHN+wK7rtqMnzlGicwJBWR11SIDFJlYBe5fVOwjHGtXX8KMyYZ4sMgSL0CoUjo4GYgIBucOtqX/RUQ=="
    crossorigin="anonymous" referrerpolicy="no-referrer" />

  <style type="text/css">
    * {
      padding: 0;
      margin: 0;
      box-sizing: border-box;
    }

    :root {
      --top-height: 50px;
    }

    body,
    html {
      width: 100%;
      height: 100%;
      margin: 0;
      font-family: "Microsoft YaHei"
    }

    #map {
      width: 100vw;
      height: 100vh;
    }

    .orgs {
      display: grid;
      grid-template-columns: repeat(2, 50%);
      grid-template-rows: repeat(2, 50%);
    }

    #info {
      position: fixed;
      right: 10px;
      top: 10px;
      background-color: #fff;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
      width: 350px;
      padding: 10px;
      text-align: center;
      border-radius: 5px;
      padding-bottom: 20px;
    }

    .orgs button {
      font-size: 18px;
      border: none;
      padding: 12px 20px;
      border-radius: 4px;
      color: #fff;
      background-color: #409eff;
      border-color: #409eff;
      cursor: pointer;
      border: 1px solid #dcdfe6;
    }

    .orgs button:hover {
      background-color: rgba(7, 193, 96, 0.8);
    }

    .orgs button.active {
      background-color: #07c160;
    }

    .hoverMethod,
    .loadMethod {
      margin-bottom: 10px;
      display: flex;
    }

    .hoverMethod div,
    .loadMethod div {
      text-align: center;
      width: 150px;
      cursor: pointer;
      height: 45px;
      line-height: 45px;
      border-radius: 5px;
    }

    .hoverMethod .active,
    .loadMethod .active {
      background-color: rgba(0, 255, 168, 0.7);
    }

    [v-cloak] {
      display: none;
    }

    #popup {
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
      background-color: #fff;
      width: 350px;
      z-index: 13;
      border-radius: 4px;
      border: 1px solid #ebeef5;
      font-size: 16px;
    }

    #popup .top {
      height: var(--top-height);
      display: flex;
      border-bottom: 1px solid #ebeef5;
      padding-left: 20px;
    }

    #popup .top .name {
      width: calc(100% - var(--top-height));
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      line-height: var(--top-height);
    }

    #popup .top .close {
      width: var(--top-height);
      color: #606266;
      cursor: pointer;
      background-color: #fff;
      border: none;
      border-left: 1px solid #ebeef5;
      font-size: 16px;
    }

    #popup .top .close:hover {
      background-color: #eee;
      color: #66b1ff;
    }

    #popup .bot {
      padding: 0 20px;
    }

    #popup .bot li {
      height: var(--top-height);
      display: flex;
      justify-content: space-between;
    }

    #popup .bot li span {
      display: inline-block;
      line-height: var(--top-height);
      width: calc(100% - 80px);
    }

    #popup .bot li span.label {
      width: 80px;
    }
  </style>

</head>

<body>
  <div id="app" v-cloak>
    <div id="map"></div>
    <div id="popup">
      <div class="top">
        <div class="name">{{clickPointInfo.name}}</div>
        <button class="close" @click='handleCloseOverlay'>
          关闭
        </button>
      </div>
      <div class="bot">
        <ul>
          <li>
            <span class="label">编码：</span>
            <span>{{clickPointInfo.adcode}}</span>
          </li>
          <li>
            <span class="label">经纬度：</span>
            <span>{{clickPointInfo.center.join(' , ')}}</span>
          </li>
        </ul>
      </div>
    </div>
    <div id="info">
      <div class="loadMethod">
        <div :class="{active: loadType === 1}" @click='loadType = 1' title="使用openlayers提供的ol.source.Vector加载GeoJSON文件">
          加载JSON方式一
        </div>
        <div :class="{active: loadType === 2}" @click='loadType = 2' title="fetch加载geojson文件">
          加载JSON方式二
        </div>
      </div>
      <div class="hoverMethod">
        <div :class="{active: hoverType === 1}" @click='changeColorBySelect'
          title="使用 ol.interaction.Select，实现鼠标悬浮区域变色">
          鼠标悬浮方式一
        </div>
        <div :class="{active: hoverType === 2}" @click='changeColorByPointerMove' title="通过鼠标移动事件，实现鼠标悬浮区域变色">
          鼠标悬浮方式二
        </div>
      </div>
      <div class="orgs">
        <button v-for='area in areaData' :key='area.code' type='button' @click='handleClickProvience(area)'
          :class='{active: currentCode == area.code}'>{{area.name}}</button>
      </div>
    </div>

  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/dist/ol.min.js"
    integrity="sha512-+nvfloZUX7awRy1yslYBsicmHKh/qFW5w79+AiGiNcbewg0nBy7AS4G3+aK/Rm+eGPOKlO3tLuVphMxFXeKeOQ=="
    crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.14/vue.global.prod.min.js"
    integrity="sha512-huEQFMCpBzGkSDSPVAeQFMfvWuQJWs09DslYxQ1xHeaCGQlBiky9KKZuXX7zfb0ytmgvfpTIKKAmlCZT94TAlQ=="
    crossorigin="anonymous" referrerpolicy="no-referrer"></script>

  <script>
    const { createApp } = Vue;
    // 悬浮样式
    const hoverStyle = new ol.style.Style({
      fill: new ol.style.Fill({
        color: 'rgba(7, 193, 96, 0.5)'
      }),
      stroke: new ol.style.Stroke({
        color: '#07c160',
        width: 2
      })
    });
    // 默认样式
    const defaultStyle = new ol.style.Style({
      fill: new ol.style.Fill({
        color: 'rgba(64, 158, 255, 0.2)'
      }),
      stroke: new ol.style.Stroke({
        color: '#409dfd',
        width: 2
      })
    });
    // 错误的默认样式，使用这个默认样式鼠标事件只在geojson的边线上触发
    const errorDefaultStyle = new ol.style.Style({
      // fill: new ol.style.Fill({
      //     color: 'rgba(255, 255, 255, 0.4)'
      // }),
      stroke: new ol.style.Stroke({
        color: '#3399CC',
        width: 2
      })
    });
    const vm = createApp({
      data() {
        return {
          map: {}, // 地图实例
          areaData: [
            {
              code: '420000_full',
              name: '湖北省',
              url: "https://geo.datav.aliyun.com/areas_v3/bound/420000_full.json",
            },
            {
              code: '430000_full',
              name: '湖南省',
              url: "https://geo.datav.aliyun.com/areas_v3/bound/430000_full.json",
            },
            {
              code: '130000_full',
              name: '河北省',
              url: "https://geo.datav.aliyun.com/areas_v3/bound/130000_full.json",
            },
            {
              code: '410000_full',
              name: '河南省',
              url: "https://geo.datav.aliyun.com/areas_v3/bound/410000_full.json",
            }
          ],
          currentCode: '', // 当前选中的省份
          currentJsonUrl: "",  // 当前选中省份的json文件地址
          loadType: 1, // 加载JSON数据的方式
          hoverType: 2, // 鼠标悬浮处理方式
          prevLayer: null, // 上一个加载的省份图层
          select: null, // select交互实例
          clickPointInfo: {
            name: '',
            center: []
          }
        }
      },
      methods: {
        // 初始化地图
        initMap() {
          // 高德地图瓦片地址
          const amapVectorLayer = new ol.layer.Tile({
            source: new ol.source.XYZ({
              url: 'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
            })
          });
          this.map = new ol.Map({
            target: 'map',
            layers: [amapVectorLayer],
            view: new ol.View({
              center: ol.proj.fromLonLat([105.481992, 36.777646]),
              zoom: 5
            })
          });
          // 绑定地图点击事件，打印经纬度
          this.map.on('click', (e) => {
            const point = ol.proj.transform(e.coordinate, 'EPSG:3857', 'EPSG:4326')
            console.log(`经度:${point[0].toFixed(6)} 纬度:${point[1].toFixed(6)}`);
          });

          // 创建一个 Overlay
          this.overlay = new ol.Overlay({
            element: document.getElementById('popup'), // 弹出框的 HTML 元素
            positioning: 'bottom-center', // 设置定位方式，这里是底部中心
            autoPan: true, // 当overlay不在视口内的时候，移动地图，让其可见
            stopEvent: false, // 是否停止事件传播
          });
          // 将Overlay添加到地图上
          this.map.addOverlay(this.overlay);
        },
        // 点击省份加载json文件
        handleClickProvience(area) {
          this.currentCode = area.code;
          this.currentJsonUrl = area.url;
          // 如果上一个json图层存在，先将其删除
          this.prevLayer && this.map.removeLayer(this.prevLayer);
          if (this.loadType === 1) {
            // 指定json的url和格式，ol自己去加载
            console.log('指定json的url和格式，ol自己去加载');
            this.olLoadJson();
          } else {
            // 请求json数据，ol读取
            console.log('请求json数据，ol读取');
            this.featchLoadJson();
          }
        },
        // ol自己去加载json文件
        olLoadJson() {
          // 加载资源
          const source = new ol.source.Vector({
            // url: `./json/${this.currentCode}.json`,
            url: this.currentJsonUrl,
            format: new ol.format.GeoJSON()
          });
          // 监听加载geojson文件进度
          source.on('featuresloadend', (event) => {
            // 获取第一个featrue的中心并跳转
            this.goToCenterByLonLat(this.prevLayer);
          });
          // 创建图层，获取geojson文件，根目录下放一个json文件夹存放geojson文件
          this.prevLayer = new ol.layer.Vector({
            source: source,
            style: defaultStyle
          });
          // 将图层添加到地图中  
          this.map.addLayer(this.prevLayer);
        },
        // featch加载json
        async featchLoadJson() {
          // 请求数据 根目录下放一个json文件夹存放geojson文件
          // const response = await fetch(`./json/${this.currentCode}.json`);
          const response = await fetch(this.currentJsonUrl);
          const json = await response.json();
          // 读取json
          const sourceJsonData = new ol.source.Vector({
            features: new ol.format.GeoJSON().readFeatures(json, { featureProjection: 'EPSG:3857' })
          });
          // 创建图层
          this.prevLayer = new ol.layer.Vector({
            source: sourceJsonData,
            style: defaultStyle
          });
          // 获取第一个featrue的中心并跳转
          this.goToCenterByLonLat(this.prevLayer);
          // 将图层添加到地图中  
          this.map.addLayer(this.prevLayer);
        },
        // 使用 ol.interaction.Select，实现鼠标悬浮区域变色
        changeColorBySelect() {
          this.hoverType = 1;
          // 使用 ol.interaction.Select时，将鼠标移动事件解绑
          this.map.un('pointermove', this.pointermoveFunc);
          // 添加交互
          this.select = new ol.interaction.Select({
            condition: ol.events.condition.pointerMove
          });
          let that = this;
          // 处理悬浮更改区域颜色
          this.select.on('select', function (event) {
            console.log('使用 ol.interaction.Select，实现鼠标悬浮区域变色', event, event.selected[0]);
            // 如果没有点击到要素，隐藏弹出框
            that.overlay && (that.overlay.getElement().style.display = 'none');

            if (event.selected.length > 0) {
              // 鼠标悬浮时应用悬浮样式
              event.selected[0].setStyle(hoverStyle);
              that.clickPointInfo = event.selected[0].values_;
              const coordinates = that.clickPointInfo.center;
              const lon = coordinates[0];
              const lat = coordinates[1];
              console.log(coordinates, 'coordinates', lon, lat);

              if (that.overlay) {
                // 设置弹出框的位置为点击的位置 
                that.overlay.setPosition(ol.proj.fromLonLat([lon, lat]));
                // 显示弹出框
                that.overlay.getElement().style.display = 'block';
              } else {
                // 如果没有点击到要素，隐藏弹出框
                that.overlay && (that.overlay.getElement().style.display = 'none');
              }

            }
            if (event.deselected.length > 0) {
              // 鼠标离开时恢复原始样式
              event.deselected[0].setStyle(null);
            }
          });
          // 添加到地图
          this.map.addInteraction(this.select);
        },
        // 通过鼠标移动事件，实现鼠标悬浮区域变色
        changeColorByPointerMove() {
          this.hoverType = 2;
          // 使用鼠标移动事件的时候，将select交互删除
          this.select && this.map.removeInteraction(this.select);
          this.map.on('pointermove', this.pointermoveFunc);
        },
        // 单独把鼠标移动的事件提出来，便于解绑使用
        pointermoveFunc(e) {
          console.log('通过鼠标移动事件，实现鼠标悬浮区域变色');
          // 根据经纬度获取feature
          const feature = this.map.forEachFeatureAtPixel(e.pixel, function (feature) {
            return feature;
          });
          // 获取不到不处理
          if (!feature) {
            this.overlay && (this.overlay.getElement().style.display = 'none');
            // 如果此时prevFeature存在，说明鼠标移出了geojson区域，将prevFeature样式恢复
            this.prevFeature && this.prevFeature.setStyle(defaultStyle);
            this.prevFeature = null;
            return;
          }
          // 如果上一个feature和获取到的feature是同一个feature，不处理，防止移动鼠标频繁设置样式
          if (this.prevFeature && this.prevFeature.ol_uid === feature.ol_uid) {
            return;
          }

          console.log(feature, 'feature', feature.getProperties());
          // 获取要素的坐标
          // const coordinates = feature.getGeometry().getCoordinates();
          this.clickPointInfo = feature.getProperties();
          const coordinates = this.clickPointInfo.center;

          if (this.overlay) {
            // 设置弹出框的位置为点击的位置 
            const lon = coordinates[0];
            const lat = coordinates[1];
            console.log(coordinates, 'coordinates', lon, lat);

            this.overlay.setPosition(ol.proj.fromLonLat([lon, lat]));
            // 显示弹出框
            this.overlay.getElement().style.display = 'block';
          } else {
            // 如果没有点击到要素，隐藏弹出框
            this.overlay && (this.overlay.getElement().style.display = 'none');
          }

          // 上一个feature存在，将其样式恢复默认
          this.prevFeature && this.prevFeature.setStyle(defaultStyle);
          // 改变feature填充颜色
          feature.setStyle(hoverStyle);

          // 存储当前高亮显示的feature
          this.prevFeature = feature;
        },
        // 获取图层的第一个包含center的feature，根据这个center坐标进行跳转
        goToCenterByLonLat(vectorLayerGeo) {
          const features = vectorLayerGeo.getSource().getFeatures();
          for (let i = 0; i < features.length; i++) {
            const feature = features[i];
            const center = feature.get('center'); // 如果存在center字段的话  
            if (center) {
              this.map.getView().animate({
                center: ol.proj.fromLonLat(center),
                duration: 500
              });
              break; // 如果找到一个center字段，就退出循环  
            }
          }
        },
        // 点击上的关闭
        handleCloseOverlay() {
          this.overlay.getElement().style.display = 'none';
        },

      },
      mounted() {
        // 初始化地图
        this.initMap();
        // 默认是使用 ol.interaction.Select，实现鼠标悬浮区域变色
        this.changeColorByPointerMove();
      }
    }).mount('#app');
  </script>
</body>

</html>